package edu.cornell.cubooks.dao.impl;

import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

import org.hibernate.Criteria;
import org.hibernate.SessionFactory;
import org.hibernate.criterion.Projections;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;

import edu.cornell.cubooks.dao.GenericDao;

/**
 * Implementation of the {@link GenericDao} which provides basic
 * functionality for CRUD operations.
 * 
 * <p>Generic DAO should be extended by other daos.</p>
 * 
 * @author CUBooks team
 *
 * @param <T>
 * @param <KeyType>
 */
@Transactional
public class GenericDaoImpl<T extends Serializable, KeyType extends Serializable> {

	@Autowired
	protected SessionFactory sessionFactory;
	
	@SuppressWarnings("unchecked")
	protected Class<T> domainClass = getDomainClass();
	
	@SuppressWarnings("unchecked")
	protected Class getDomainClass() {
	    if (domainClass == null) {
	    	ParameterizedType thisType = (ParameterizedType) getClass().getGenericSuperclass();
	        domainClass = (Class) thisType.getActualTypeArguments()[0];
	    }
	    return domainClass;
	}
	
	@SuppressWarnings("unchecked")
	@Transactional(readOnly = true)
	public T load(KeyType id) {
		return (T) sessionFactory.getCurrentSession().load(domainClass, id);
	}
	
	@SuppressWarnings("unchecked")
	@Transactional(readOnly = true)
	public T get(KeyType id) {
		return (T) sessionFactory.getCurrentSession().get(domainClass, id);
	}
    
	@Transactional(readOnly = false)
	public void update(T t) {
		sessionFactory.getCurrentSession().update(t);
	}
	
	@SuppressWarnings("unchecked")
	@Transactional(readOnly = false)
	public T save(T t) {
		//sessionFactory.getCurrentSession().save(t);
		return (T) sessionFactory.getCurrentSession().merge(t);
	}
	
	@Transactional(readOnly = false)
	public void delete(T t) {
		sessionFactory.getCurrentSession().delete(t);
	}
	
	@SuppressWarnings("unchecked")
	@Transactional(readOnly = true)
	public Set<T> getAll() {
		Criteria criteria = sessionFactory.getCurrentSession().createCriteria(domainClass);
		return toSet(criteria.list());
	}
	
	@Transactional(readOnly = false)
	public void deleteById(KeyType id) {
		Object obj = load(id);
		sessionFactory.getCurrentSession().delete(obj);
	}
	
	@Transactional(readOnly = true)
	public int count() {
		Criteria criteria = sessionFactory.getCurrentSession().createCriteria(domainClass.getName()); 
		criteria.setProjection(Projections.rowCount());
		return ((Integer)criteria.list().get(0)).intValue();
	}

	public Set<T> toSet(List<T> t) {
		Set<T> set = new LinkedHashSet<T>();
		set.addAll(t);  
		return set;
	}
	
	public SessionFactory getSessionFactory() {
		return sessionFactory;
	}
}